package org.sword.wechat4j.util;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 异步的HTTP/HTTPS请求器
 */
public class SingletonHttpClient {

    private static SingletonHttpClient client;

    private SingletonHttpClient(List<String> urls, Integer maxTotal) {
        if (urls != null)
            for (String url : urls)
                try {
                    String hostname = url.split("/")[2];
                    int port = 80;
                    if (hostname.contains(":")) {
                        String[] arr = hostname.split(":");
                        hostname = arr[0];
                        port = Integer.parseInt(arr[1]);
                    }
                    hosts.add(hostname + ":" + port);
                } catch (Exception e) {
                }
        if (maxTotal != null)
            this.maxTotal = maxTotal;
    }

    public static SingletonHttpClient getClient(List<String> urls, Integer base) {
        if (client == null)
            client = new SingletonHttpClient(urls, base);
        return client;
    }

    public static SingletonHttpClient getDefaultClient() {
        return getClient(null, null);
    }

    private static final int timeOut = 10 * 1000;

    /**
     * 初始池大小、路由基础连接数
     */
    private int maxTotal = 100;

    private static CloseableHttpClient httpClient;

    private Set<String> hosts = new HashSet<>();

    private final static Object syncLock = new Object();

    private void config(HttpRequestBase httpRequestBase) {
        // 设置Header等
        // httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
        // httpRequestBase
        // .setHeader("Accept",
        // "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        // httpRequestBase.setHeader("Accept-Language",
        // "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");// "en-US,en;q=0.5");
        // httpRequestBase.setHeader("Accept-Charset",
        // "ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");

        // 配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(timeOut)
                .setConnectTimeout(timeOut).setSocketTimeout(timeOut).build();
        httpRequestBase.setConfig(requestConfig);
    }

    /**
     * 获取HttpClient对象
     *
     * @return CloseableHttpClient
     */
    public CloseableHttpClient getHttpClient() {
        if (httpClient == null)
            synchronized (syncLock) {
                if (httpClient == null)
                    httpClient = createHttpClient();
            }
        return httpClient;
    }

    private CloseableHttpClient createHttpClient() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
                        .build());
        cm.setMaxTotal(hosts.size() == 0 ? maxTotal : maxTotal * hosts.size());
        cm.setDefaultMaxPerRoute(cm.getMaxTotal());
        for (String host : hosts) {
            String[] split = host.split(":");
            HttpHost httpHost = new HttpHost(split[0], Integer.parseInt(split[1]));
            cm.setMaxPerRoute(new HttpRoute(httpHost), maxTotal / hosts.size());
        }
        return HttpClients.custom().setConnectionManager(cm).setRetryHandler(new HttpRequestRetryHandler() {
            // 请求重试处理
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= 5) {// 如果已经重试了5次，就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                    return false;
                }
                if (exception instanceof InterruptedIOException) {// 超时
                    return false;
                }
                if (exception instanceof UnknownHostException) {// 目标服务器不可达
                    return false;
                }
                /*if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                    return false;
                }*/
                if (exception instanceof SSLException) {// SSL握手异常
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        }).build();
    }

    private void setPostParams(HttpPost httpost, Map<String, Object> params) {
        /*List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        Set<String> keySet = params.keySet();
        for (String key : keySet) {
            nvps.add(new BasicNameValuePair(key, params.get(key).toString()));
        }
        try {
            httpost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }*/

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        for (String key : params.keySet()) {
            Object value = params.get(key);
            if (value instanceof File) {
                builder.addPart(key, new FileBody((File) value));
            } else {
                builder.addPart(key, new StringBody(String.valueOf(value), ContentType.create("text/plain", "UTF-8")));
            }
        }
        httpost.setEntity(builder.build());
    }

    /**
     * POST 请求URL获取内容
     *
     * @param url url
     * @return responseString
     * @throws IOException
     */
    public String post(String url, Map<String, Object> params) throws IOException {
        HttpPost httppost = new HttpPost(url);
        setPostParams(httppost, params);
        HttpEntity entity = execute(httppost);
        String result = EntityUtils.toString(entity, "utf-8");
        EntityUtils.consume(entity);
        return result;
    }

    /**
     * JSON参数的POST请求
     *
     * @param url
     * @param params
     * @return
     * @throws IOException
     */
    public String post(String url, String params) throws IOException {
        HttpPost httppost = new HttpPost(url);
        httppost.setEntity(new StringEntity(params, "UTF-8"));
        HttpEntity entity = execute(httppost);
        String result = EntityUtils.toString(entity, "utf-8");
        EntityUtils.consume(entity);
        return result;
    }

    private HttpEntity execute(HttpRequestBase requestBase) throws IOException {
        config(requestBase);
        return getHttpClient().execute(requestBase, HttpClientContext.create()).getEntity();
    }

    /**
     * GET请求URL获取内容
     *
     * @param url 地址
     * @return responseString
     * @throws IOException
     */
    public String get(String url) throws IOException {
        HttpEntity entity = execute(new HttpGet(url));
        Header contentType = entity.getContentType();
        System.err.println(contentType);
        String result = EntityUtils.toString(entity, "utf-8");
        EntityUtils.consume(entity);
        return result;
    }

    /**
     * GET请求URL获取内容
     *
     * @param url 地址
     * @return 文档流
     * @throws IOException
     */
    public HttpEntity getStream(String url) throws IOException {
        return execute(new HttpGet(url));
    }

    public void shutdown() {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
